{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 二、图像处理\n",
    "\n",
    "## 2.1 插值算法与几何变换\n",
    "\n",
    "该部分将对基本的几何变换进行学习，几何变换的原理大多都是相似，只是变换矩阵不同，因此，我们以最常用的平移和旋转为例进行学习。在深度学习领域，我们常用平移、旋转、镜像等操作进行数据增广；在传统CV领域，由于某些拍摄角度的问题，我们需要对图像进行矫正处理，而几何变换正是这个处理过程的基础，因此了解和学习几何变换也是有必要的。\n",
    "\n",
    "### 2.1.1 仿射变换\n",
    "\n",
    "仿射变换矩阵：\n",
    "\n",
    "$$\n",
    "\\begin{bmatrix}x \\\\y\\\\1\\end{bmatrix} \n",
    "=\\begin{bmatrix}\n",
    "a_0 &a_1 & a_2 \\\\\n",
    "a_3 & a_4 & a_5 \\\\\n",
    "0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "x_0 \\\\\n",
    "y_0 \\\\\n",
    "1\n",
    "\\end{bmatrix}$$\n",
    "\n",
    "其中$x$，$y$表示输出图像像素的坐标，$x_0$,$y_0$表示输入图像像素的坐标"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "|变换名称| $a_0$ |  $a_1$ | $a_2$ | $a_3$ | $a_4$ | $a_5$ |\n",
    "|-|-|-|-|-|-|-|\n",
    "| 平移 | 1 |0| $\\triangle x$| 0 |1|$\\triangle y$\n",
    "| 均匀缩放 |  $s$|0| 0| 0 |$s$|0\n",
    "|  不均匀缩放| $s_x$ |0|0 |0  |$s_y$|0\n",
    "| 顺时针旋转角度$\\theta$ |  $cos\\theta$|$sin\\theta$|0 | $-sin\\theta$ |$cos\\theta$|0\n",
    "| 逆时针旋转角度$\\theta$ |  $cos\\theta$|$-sin\\theta$|0 | $sin\\theta$ |$cos\\theta$|0\n",
    "|  垂直偏移变换| 1 |0| 0| h |1|0\n",
    "|  水平偏移变换| 1 |h| 0| 0 |1|0 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "例子：\n",
    "\n",
    "原图如下：\n",
    "<div  align=\"center\">    \n",
    "<img src=\"https://img-blog.csdnimg.cn/20200422224925719.png\" width = \"40%\" />\n",
    "</div>\n",
    "\n",
    "1. 放大为原来的两倍\n",
    "$$\n",
    "\\begin{bmatrix}x \\\\y\\\\1\\end{bmatrix} \n",
    "=\\begin{bmatrix}\n",
    "2 &0 & 0 \\\\\n",
    "0 & 2 & 0 \\\\\n",
    "0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "x_0 \\\\\n",
    "y_0 \\\\\n",
    "1\n",
    "\\end{bmatrix}\n",
    "$$\n",
    "\n",
    "<div  align=\"center\">    \n",
    "<img src=\"https://img-blog.csdnimg.cn/20200422230159292.png\" width = \"40%\" />\n",
    "</div>\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from tensorbay import GAS\n",
    "from tensorbay.dataset import Segment\n",
    "import cv2\n",
    "from PIL import Image\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 不要打开VPN\n",
    "# Authorize a GAS client.\n",
    "\n",
    "gas=GAS('Accesskey-2e96f1bcbe77a14b7fccfab458728754')\n",
    "# Get a dataset client.\n",
    "dataset_client=gas.get_dataset(\"DogsVsCats-1\")\n",
    "# List dataset segments.\n",
    "segments=dataset_client.list_segment_names()\n",
    "# Get a segment by name\n",
    "segment=Segment(\"train\", dataset_client)\n",
    "\n",
    "data = segment[4] # 取第五张图片\n",
    "buf = np.asarray(bytearray(data.open().read()), dtype=\"uint8\")\n",
    "img = cv2.imdecode(buf, cv2.IMREAD_COLOR)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def resize_front_nearest(src, s):\n",
    "    h, w = src.shape\n",
    "    dest_h, dest_w = int(h*s), int(w*s)\n",
    "    dest = np.zeros((int(dest_h), int(dest_w)))\n",
    "    for i in range(h):\n",
    "        for j in range(w):\n",
    "            dest_i, dest_j = int(s*i), int(s*j)\n",
    "            dest[dest_i, dest_j] = src[i, j]\n",
    "    return dest"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def resize_back_nearest(src, s):\n",
    "    h, w = src.shape\n",
    "    dest_h, dest_w = int(h*s), int(w*s)\n",
    "    dest = np.zeros((int(dest_h), int(dest_w), 3))\n",
    "    for i in range(dest_h):\n",
    "        for j in range(dest_w):\n",
    "            src_i, src_j = int(1/s*i), int(1/s*j)\n",
    "            dest[i, j] = src[src_i, src_j]\n",
    "    return dest"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def resize_back_linear(src, s):\n",
    "    h, w = src.shape\n",
    "    dest_h, dest_w = int(h*s), int(w*s)\n",
    "    dest = np.zeros((int(dest_h), int(dest_w), 3))\n",
    "    for i in range(dest_h):\n",
    "        for j in range(dest_w):              \n",
    "            src_i, src_j = 1/s*i , 1/s*j\n",
    "            x = src_j-int(src_j)\n",
    "            y = src_i-int(src_i)\n",
    "            if src_i >= (h-1):\n",
    "                y, y0, y1 = 0, h-1, h-1\n",
    "            else:\n",
    "                y0, y1 = int(src_i), int(src_i)+1\n",
    "            if src_j >= (w-1):\n",
    "                x, x0, x1 = 0, w-1, w-1\n",
    "            else:\n",
    "                x0, x1 = int(src_j), int(src_j)+1\n",
    "            f_00 = src[y0, x0]\n",
    "            f_01 = src[y1, x0]\n",
    "            f_10 = src[y0, x1]\n",
    "            f_11 = src[y1, x1]\n",
    "            dest[i, j] = (f_10-f_00)*x + (f_01-f_00)*y + (f_11+f_00-f_01-f_10)*x*y + f_00\n",
    "    return dest"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "<ipython-input-8-d1ef18aefae5>:1: DeprecationWarning: `np.float` is a deprecated alias for the builtin `float`. To silence this warning, use `float` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.float64` here.\n",
      "Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
      "  img = cv2.imread('a.png', 0).astype(np.float)\n"
     ]
    }
   ],
   "source": [
    "img = cv2.imread('a.png', 0).astype(np.float)\n",
    "resized_img_n = resize_back_nearest(img, 5).astype(np.uint8)\n",
    "resized_img_l = resize_back_linear(img, 5).astype(np.uint8)\n",
    "# resized_img_l = cv2.resize(resized_img_l, dsize=None, fx=5, fy=5, interpolation = cv2.INTER_LINEAR)\n",
    "# resized_img_n = cv2.resize(resized_img_n, dsize=None, fx=5, fy=5, interpolation = cv2.INTER_NEAREST)\n",
    "\n",
    "cv2.imshow('origin', img)\n",
    "cv2.imshow('nearest', resized_img_n)\n",
    "cv2.imshow('linear', resized_img_l)\n",
    "key = cv2.waitKey()\n",
    "if key == 27:\n",
    "    cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.2.1平移变换\n",
    "平移变换,图像向下平移$d_y$个单位，向右平移$d_x$个单位\n",
    "$$\n",
    "\\begin{bmatrix}x \\\\y\\\\1\\end{bmatrix}  =\n",
    "\\begin{bmatrix}\n",
    "1 &0 & d_x \\\\\n",
    "0 & 1 & d_y \\\\\n",
    "0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "x_0 \\\\\n",
    "y_0 \\\\\n",
    "1\n",
    "\\end{bmatrix}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "笛卡尔坐标系中向上平移一个单位向右平移一个单位\n",
    "$$\n",
    "\\begin{bmatrix}x \\\\y\\\\1\\end{bmatrix} \n",
    "=\\begin{bmatrix}\n",
    "1 &0 & 1 \\\\\n",
    "0 & 1 & 1 \\\\\n",
    "0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "x_0 \\\\\n",
    "y_0 \\\\\n",
    "1\n",
    "\\end{bmatrix}\n",
    "$$\n",
    "\n",
    "\n",
    "<div  align=\"center\">    <img src=\"https://img-blog.csdnimg.cn/20200422225300652.png\" width = \"40%\" /></div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "shape is  149 150 3\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import numpy as np\n",
    "# img = cv2.imread('a.png', 1) # .astype(float)\n",
    "\n",
    "h,w,c = img.shape # 高，宽，通道数\n",
    "print(\"shape is \",h,w,c)\n",
    "cv2.imshow(\"cat\", img)\n",
    "mat_translation=np.float32([[1,0,20],[0,1,50]])  #变换矩阵：设置平移变换所需的计算矩阵：2行3列\n",
    "#[[1,0,20],[0,1,50]]   表示平移变换：其中20表示水平方向上的平移距离，50表示竖直方向上的平移距离。\n",
    "dst=cv2.warpAffine(img,mat_translation,(w+20,h+50))  #变换函数\n",
    "'''\n",
    "参数2 变换矩阵：是一个2行3列的矩阵，由这个矩阵决定是何种变换\n",
    "参数3 变换后输出图像的大小:(width+20,height+50)-->宽和高(自己规定)\n",
    "参数4 可选参数，用于设置插值方法的组合，默认为INTER_LINEAR使用线性插值算法，除了图像缩放中用到的几个插值算法外，仿射变换还可以选用\n",
    "INTER_LANCZOS4（Lanczos插值算法）。\n",
    "参数5 borderValue：可选参数，在边界不变的情况下可以使用的值，主要用于设置边界的填充值，默认为0\n",
    "'''\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.2.3 旋转变换\n",
    " 笛卡尔坐标系下逆时针旋转$\\theta$度\n",
    "\n",
    "像素坐标系下顺时针旋转$\\theta$度\n",
    "\n",
    "\n",
    "$$\n",
    "\\begin{bmatrix}x \\\\y\\\\1\\end{bmatrix} \n",
    "=\\begin{bmatrix}\n",
    "cos\\theta &-sin\\theta & 0 \\\\\n",
    "sin\\theta & cos\\theta & 0 \\\\\n",
    "0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "x_0 \\\\\n",
    "y_0 \\\\\n",
    "1\n",
    "\\end{bmatrix}\n",
    "$$\n",
    "顺时针旋转45度\n",
    "$$\n",
    "\\begin{bmatrix}x \\\\y\\\\1\\end{bmatrix} \n",
    "=\\begin{bmatrix}\n",
    "\\sqrt2/2 &\\sqrt2/2 & 0 \\\\\n",
    "-\\sqrt2/2 & \\sqrt2/2 & 0 \\\\\n",
    "0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "x_0 \\\\\n",
    "y_0 \\\\\n",
    "1\n",
    "\\end{bmatrix}\n",
    "$$\n",
    "\n",
    "<div  align=\"center\">    \n",
    "<img src=\"https://img-blog.csdnimg.cn/20200422231055464.png\" width = \"40%\" />\n",
    "</div>\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cv2.imshow(\"cat\", img)\n",
    "mat_translation=cv2.getRotationMatrix2D((w / 2, h / 2), 90, 1)   ##center, angle, scale   像素坐标系下的逆时针变换角度\n",
    "dst=cv2.warpAffine(img,mat_translation,(w+20,h+50))  #变换函数\n",
    "'''\n",
    "参数2 变换矩阵：是一个2行3列的矩阵，由这个矩阵决定是何种变换\n",
    "参数3 变换后输出图像的大小:(width+20,height+50)-->宽和高(自己规定)\n",
    "参数4 可选参数，用于设置插值方法的组合，默认为INTER_LINEAR使用线性插值算法，除了图像缩放中用到的几个插值算法外，仿射变换还可以选用\n",
    "INTER_LANCZOS4（Lanczos插值算法）。\n",
    "参数5 borderValue：可选参数，在边界不变的情况下可以使用的值，主要用于设置边界的填充值，默认为0\n",
    "'''\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cv2.imshow(\"cat\", img)\n",
    "# mat_translation=np.float32([[0.7,-0.7,0],[0.7,0.7,0]])  #\n",
    "# mat_translation=np.float32([[np.cos(np.pi/4),-np.sin(np.pi/4),0],[np.sin(np.pi/4),np.cos(np.pi/4),0]])   # 像素坐标系下顺时针45度\n",
    "mat_translation=np.float32([[np.sqrt(2)/2,np.sqrt(2)/2,0],[-np.sqrt(2)/2,np.sqrt(2)/2,0]])   # 像素坐标系下逆时针45度\n",
    "dst=cv2.warpAffine(img,mat_translation,(w,h))  #变换函数\n",
    "'''\n",
    "参数2 变换矩阵：是一个2行3列的矩阵，由这个矩阵决定是何种变换\n",
    "参数3 变换后输出图像的大小:(width+20,height+50)-->宽和高(自己规定)\n",
    "参数4 可选参数，用于设置插值方法的组合，默认为INTER_LINEAR使用线性插值算法，除了图像缩放中用到的几个插值算法外，仿射变换还可以选用\n",
    "INTER_LANCZOS4（Lanczos插值算法）。\n",
    "参数5 borderValue：可选参数，在边界不变的情况下可以使用的值，主要用于设置边界的填充值，默认为0\n",
    "'''\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.2.4.错切变换 \n",
    "水平偏移两个单位\n",
    "\n",
    "$$\n",
    "\\begin{bmatrix}x \\\\y\\\\1\\end{bmatrix} \n",
    "=\\begin{bmatrix}\n",
    "1 & 2 & 0 \\\\\n",
    "0 & 1 & 0 \\\\\n",
    "0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "x_0 \\\\\n",
    "y_0 \\\\\n",
    "1\n",
    "\\end{bmatrix}\n",
    "$$\n",
    "<div  align=\"center\">    \n",
    "<img src=\"https://img-blog.csdnimg.cn/20200422232721460.png\" width = \"40%\" />\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mat_translation=np.float32([[1,1.2,0],[0,1,0]]) \n",
    "dst=cv2.warpAffine(img,mat_translation,(w,h))  #变换函数\n",
    "\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "总结：\n",
    "矩阵的主对角线负责缩放，负对角线负责错切或旋转，第三列负责平移。\n",
    "仿射变换是从一个二维坐标系变换到另一个二维坐标系，属于线性变换。通过已知3对坐标点可以求得变换矩阵。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cv2.imshow(\"cat\", img)\n",
    "\n",
    "pts1 = np.float32([[0, 0], [w - 1, 0], [0, h - 1]])\n",
    "pts2 = np.float32([[w * 0.2, h * 0.1], [w * 0.9, h * 0.2], [w * 0.1, h * 0.9]])\n",
    " \n",
    "M = cv2.getAffineTransform(pts1, pts2)\n",
    "dst = cv2.warpAffine(img, M, (w, h))\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1.2 图像旋转、偏移相关问题\n",
    "\n",
    "**问题1：**\n",
    "\n",
    "对于缩放、平移可以以图像坐标原点（图像左上角为原点）为中心变换，这不用坐标系变换，直接按照一般形式计算即可。而对于旋转和偏移，一般是以图像中心为原点，那么这就涉及坐标系转换了。\n",
    "\n",
    "我们都知道，图像坐标的原点在图像左上角，水平向右为 X 轴，垂直向下为 Y 轴。数学课本中常见的坐标系是以图像中心为原点，水平向右为 X 轴，垂直向上为 Y 轴，称为笛卡尔坐标系。看下图:  \n",
    "\n",
    "<div  align=\"center\">    \n",
    "<img src=\"https://img-blog.csdnimg.cn/2020042320032039.png\" width = \"40%\" />\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "因此，对于旋转和偏移，就需要3步（3次变换）：\n",
    "\n",
    "* 将输入原图图像坐标转换为笛卡尔坐标系；\n",
    "* 进行旋转计算。旋转矩阵前面已经给出了；\n",
    "* 将旋转后的图像的笛卡尔坐标转回图像坐标。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "先看下图：  \n",
    "\n",
    "<div  align=\"center\">    \n",
    "<img src=\"https://img-blog.csdnimg.cn/20200423200259503.png\" width = \"40%\" />\n",
    "</div>\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "**令图像表示为M×N的矩阵，对于点A而言，两坐标系中的坐标分别是(0，0)和$(-N/2,M/2)$，则图像某像素点$(x',y')$转换为笛卡尔坐标$（x,y）$转换关系为，$x$为列，$y$为行：**   \n",
    "\n",
    "\n",
    "$$x=x'-\\frac{N}{2}$$\n",
    "\n",
    "$$y=-y'-\\frac{M}{2}$$\n",
    "\n",
    "逆变换为：  \n",
    "\n",
    "$$x'=x+\\frac{N}{2}$$\n",
    "\n",
    "$$y'=-y+\\frac{M}{2}$$\n",
    "\n",
    "\n",
    "&emsp;&emsp;于是，根据前面说的3个步骤（3次变换），旋转(顺时针旋转)的变换形式就为，3次变换就有3个矩阵：  \n",
    "\n",
    "$$\n",
    "\\begin{bmatrix}x \\\\y\\\\1\\end{bmatrix} \n",
    "=\\begin{bmatrix}\n",
    "1&0 & -0.5\\cdot N  \\\\\n",
    "0 & -1 & -0.5 \\cdot M \\\\\n",
    "0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "cos\\theta&sin\\theta & 0  \\\\\n",
    "-sin\\theta & cos\\theta & 0 \\\\\n",
    "0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "1&0 & 0.5\\cdot N  \\\\\n",
    "0 & -1 & 0.5 \\cdot M \\\\\n",
    "0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "x_0 \\\\\n",
    "y_0 \\\\\n",
    "1\n",
    "\\end{bmatrix}$$\n",
    "\n",
    "即：\n",
    "$$\n",
    "\\begin{bmatrix}x \\\\y\\\\1\\end{bmatrix} =\n",
    "\\begin{bmatrix}\n",
    "cos\\theta&-sin\\theta & -0.5N(1-cos\\theta)+0.5Msin\\theta  \\\\\n",
    "sin\\theta & -cos\\theta & -0.5M(1-sin\\theta)-0.5Mcos\\theta \\\\\n",
    "0 & 0 & 1\n",
    "\\end{bmatrix}\n",
    "\\begin{bmatrix}\n",
    "x_0 \\\\\n",
    "y_0 \\\\\n",
    "1\n",
    "\\end{bmatrix}$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "仿射变换是一种二维坐标（x, y）到二维坐标（u, v）的线性变换。\n",
    "仿射变换保持了二维图像的“平直性”和“平行性”。\n",
    "\n",
    "平直性：\n",
    "- 直线经仿射变换后还是直线\n",
    "- 圆弧经仿射变换后还是圆弧\n",
    "\n",
    "平行性：\n",
    "\n",
    "- 直线之间的相对位置关系保持不变\n",
    "- 平行线经仿射变换后依然为平行线\n",
    "- 直线上点的位置顺序不会发生变化\n",
    "- 向量间夹角可能会发生变化"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2.2.5 透视变换\n",
    "透视变换是从一个二维坐标系变换到一个三维坐标系，属于非线性变换。通过已知4对坐标点可以求得变换矩阵。\n",
    "最直观的原则就是近大远小。生活中很常见的一种变换。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import cv2\n",
    "\n",
    "h,w,c = img.shape # 高，宽，通道数\n",
    "\n",
    "\n",
    "cv2.imshow(\"cat\", img)\n",
    "\n",
    "src = np.array([[0,0],  [w-1,0],  [0,h-1], [w-1,h-1]], np.float32)\n",
    "dst = np.array([[100,50], [w/2,50], [100,h-1], [w-1,h-1]], np.float32)\n",
    "A1 = cv2.getPerspectiveTransform(src, dst)\n",
    "dst = cv2.warpPerspective(img, A1, (w, h), borderValue = 125)\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-1"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import cv2\n",
    "img1 = cv2.imread('test.jpg', 1) # .astype(float)\n",
    "\n",
    "h,w,c = img1.shape # 高，宽，通道数\n",
    "\n",
    "\n",
    "cv2.imshow(\"cat\", img1)\n",
    "\n",
    "src = np.array([[393,319],  [770,201],  [407,499], [772,331]], np.float32)\n",
    "dst = np.array([[10,10], [w,10], [10,h-1], [w-1,h-1]], np.float32)\n",
    "A1 = cv2.getPerspectiveTransform(src, dst)\n",
    "dst = cv2.warpPerspective(img1, A1, (w, h), borderValue = 125) # bordervalue:边界的颜色设置\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "647 1020 3\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import cv2\n",
    "img = cv2.imread('road.png', 1) # .astype(float)\n",
    "\n",
    "h,w,c = img.shape # 高，宽，通道数\n",
    "\n",
    "print(h,w,c)\n",
    "cv2.imshow(\"cat\", img)\n",
    "\n",
    "src = np.array([[433,281],  [613,281],  [97,417], [907,417]], np.float32)\n",
    "dst = np.array([[10,10], [w,10], [10,h-1], [w-1,h-1]], np.float32)\n",
    "A1 = cv2.getPerspectiveTransform(src, dst)\n",
    "dst = cv2.warpPerspective(img, A1, (w, h), borderValue = 125)\n",
    "cv2.imshow('dst',dst)\n",
    "cv2.waitKey(0)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "借鉴的资料来源:\n",
    "https://www.jianshu.com/p/7e701d7bfd79"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "eca498e910dd22253662641d0fd16f8e7325793442e0ac595e9db974b4611c50"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
